# coding: utf-8
import json
import shutil
from dataclasses import dataclass, field
from datetime import datetime
from pathlib import Path
from typing import Union, Any, List
from uuid import uuid4

from PySide6.QtGui import QImage, QPixmap
from sqlalchemy import Column, String, DateTime, Text, Float, Integer
from sqlalchemy.orm import Session

from .db import DbBase


def _to_dict(obj):
    return {
        'id': obj.id,
        'url': obj.url,
        'file_name': obj.file_name,
        'save_folder': obj.save_folder,
        'size': obj.size,
        'status': obj.status,
        'create_time': obj.create_time,
        'cover': obj.cover,
        'progress': obj.progress,
        'downloaded_size': obj.downloaded_size,
        'headers': obj.headers,
        'type': obj.type
    }


@dataclass
class Task:
    id: str = field(default_factory=lambda: uuid4().hex, init=True)
    url: str = None
    file_name: str = None
    save_folder: str = None
    size: str = None
    status: str = None
    cover: Union[str, QImage, QPixmap] = None
    progress: float = 0
    downloaded_size: int = 0
    headers: Union[dict, str] = None
    type: str = None
    create_time: Union[DateTime, str, Any] = field(default_factory=datetime.now, init=True)

    def to_dict(self):
        return _to_dict(self)


class T_TaskModel(DbBase):
    __tablename__ = 'tbl_task'
    id = Column(String, primary_key=True, insert_default=lambda: uuid4().hex)
    url = Column(Text, nullable=False)
    file_name = Column(Text, nullable=False)
    save_folder = Column(Text, nullable=False)
    size = Column(Text, nullable=True)
    status = Column(Text, nullable=True)
    cover = Column(Text, nullable=True)
    progress = Column(Float, nullable=True, default=0)
    downloaded_size = Column(Integer, nullable=True, default=0)
    headers = Column(Text, nullable=True)
    type = Column(Text, nullable=True)
    create_time = Column(DateTime, autoincrement=True, insert_default=datetime.now)

    def to_dict(self):
        return {c.name: getattr(self, c.name, None) for c in self.__table__.columns}

    def to_task(self):
        params = self.to_dict()
        params['headers'] = json.loads(params['headers'])
        return Task(**params)

    @staticmethod
    def from_task(task: Task):
        params = task.to_dict()
        params['headers'] = str(params['headers'])
        return T_TaskModel(**params)

    def __str__(self):
        return f'T_TaskModel(**{self.to_dict()})'


class TaskDao:
    @staticmethod
    def add(task: Task, session: Session) -> None:
        old = session.query(T_TaskModel).filter_by(id=task.id).first()
        if old:
            return
        task_model = T_TaskModel.from_task(task)
        session.add(task_model)
        session.commit()
        return

    @staticmethod
    def get_all(session: Session) -> List[Task]:
        task_models = session.query(T_TaskModel).order_by(T_TaskModel.create_time.desc()).all()
        tasks = [task_model.to_task() for task_model in task_models]
        return tasks

    @staticmethod
    def get_by_id(id: str, session: Session) -> Union[None, Task]:
        task_model = session.query(T_TaskModel).filter_by(id=id).first()
        if task_model:
            return task_model.to_task()
        else:
            return None

    @staticmethod
    def delete_by_id(id: str, session: Session, *, delete_file: bool = True, ) -> None:
        task_model = session.query(T_TaskModel).filter_by(id=id).first()
        if task_model:
            session.delete(task_model)
            session.commit()
            if delete_file:
                save_folder = Path(str(task_model.save_folder))
                file_name = Path(str(task_model.file_name))
                (save_folder / file_name).unlink(missing_ok=True)
                (save_folder / file_name.with_suffix('.temp')).unlink(missing_ok=True)
                shutil.rmtree(save_folder / f'{file_name.stem}_temp', ignore_errors=True)

            Path(str(task_model.cover)).unlink(missing_ok=True)

    @staticmethod
    def update_by_id(task: Task, session: Session) -> None:
        task_model = session.query(T_TaskModel).filter_by(id=task.id).first()
        if task_model:
            for key, value in task.to_dict().items():
                if value is not None:
                    setattr(task_model, key, value)
            session.commit()

    @staticmethod
    def clear(session: Session) -> None:
        session.query(T_TaskModel).delete()
        session.commit()
